package com.heima.search.service.impl;

import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.search.dtos.UserSearchDto;
import com.heima.search.pojos.ApUserSearch;
import com.heima.search.service.SearchService;
import com.heima.utils.threadlocal.AppThreadLocalUtil;
import org.apache.commons.lang3.StringUtils;
import org.bson.types.ObjectId;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilders;
import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

@Service
public class SearchServiceImpl implements SearchService {

    @Autowired
    private RestHighLevelClient client;

    @Autowired
    private MongoTemplate mongoTemplate;
    /**
     * 基本搜索业务
     *
     * @param dto
     * @return
     */
    @Override
    public ResponseResult search(UserSearchDto dto) {
        //声明一个集合，用来承载展示的数据
        List<Map> list=new ArrayList<>();


        //2、创建搜索请求对象，并指定索引库名称
        SearchRequest searchRequest=new SearchRequest("app_info_article");
        //3.创建布尔查询
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //根据关键字查询
        if(StringUtils.isNotEmpty(dto.getSearchWords())){
            boolQuery.must(QueryBuilders.matchQuery("all",dto.getSearchWords()));
        }
        if(dto.getMinBehotTime()!=null){
            //根据时间查询
            boolQuery.filter(QueryBuilders.rangeQuery("publishTime").lte(dto.getMinBehotTime()));
        }
        searchRequest.source().query(boolQuery);

        //4.分页
        searchRequest.source().from(dto.getPageNum()).size(dto.getPageSize());

        //5.排序
        searchRequest.source().sort("publishTime", SortOrder.DESC);

        //6.高亮设置
        searchRequest.source().highlighter(
                new HighlightBuilder()
                .field("title")
                .preTags("<font style='color: red; font-size: inherit;'>")
                .postTags("</font>")
                .requireFieldMatch(false)
        );


        //1.执行搜索请求操作
        try {
            SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);

            //7.解析结果
            SearchHit[] hits = response.getHits().getHits();
            for (SearchHit hit : hits) {
                //获取到每一个文档
                Map<String, Object> hitSourceAsMap = hit.getSourceAsMap();

                //高亮设置
                HighlightField highlightField = hit.getHighlightFields().get("title");
                //有高亮设置
                if(highlightField.getFragments().length>0){
                    String hTitel = highlightField.getFragments()[0].toString();
                    hitSourceAsMap.put("h_title",hTitel);
                }else{
                //没有高亮设置
                    hitSourceAsMap.put("h_title",hitSourceAsMap.get("title"));
                }
                list.add(hitSourceAsMap);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        //异步调用保存历史记录的方法
        this.saveMongoDb(dto.getSearchWords(), AppThreadLocalUtil.getUser().getId());

        return ResponseResult.okResult(list);
    }

    /**
     * 保存用户搜索历史记录
     *
     * @param keyword
     * @param userId
     */
    @Override
    @Async
    public void saveMongoDb(String keyword, Integer userId) {
        //游客身份不可以执行以下业务
        if(userId==0){
            return;
        }

        //1.根据关键字查询mongodb
        ApUserSearch apUserSearch = mongoTemplate.findOne(Query.query(Criteria.where("keyword").is(keyword).and("userId").is(userId)), ApUserSearch.class);

        //2.判断是否存在，如果存在，则更新时间
        if(apUserSearch!=null){
            apUserSearch.setCreatedTime(new Date());
            mongoTemplate.save(apUserSearch);
            return;
        }

        //3.如果不存在，则判断当前用户下的历史记录是否超过10条
        List<ApUserSearch> searchList = mongoTemplate.find(
                Query.query(Criteria.where("userId").is(userId)).with(Sort.by(Sort.Order.desc("createdTime"))),
                ApUserSearch.class);


        //3.5 封装新的数据
        apUserSearch=new ApUserSearch();
        apUserSearch.setId(ObjectId.get().toString());
        apUserSearch.setUserId(userId);
        apUserSearch.setKeyword(keyword);
        apUserSearch.setCreatedTime(new Date());

        //4.如果不超过10条，则直接封装数据，然后新增
        if(searchList==null || searchList.size()<10){
             mongoTemplate.save(apUserSearch);
        }else {
            //5.如果超过10条，则替换最后一条数据(最早的数据)
            //5.1获取最后一条数据
            ApUserSearch lastApUserSearch = searchList.get(searchList.size() - 1);
            /**
             *  5.2替换
             *  findAndReplace(Query query, T replacement)
             *  参数1表示条件，查询最后一条的查询条件，被替换
             *  参数2表示要替换的数据实体
             */
            mongoTemplate.findAndReplace(Query.query(Criteria.where("id").is(lastApUserSearch.getId())),apUserSearch);
        }
    }

    /**
     * 自动补全功能
     *
     * @param dto
     * @return
     */
    @Override
    public ResponseResult load(UserSearchDto dto) {
        //2.创建搜索请求对象，并指定索引库名称
        SearchRequest searchRequest=new SearchRequest("app_info_article");

        //3.执行DSL语句
        searchRequest.source().suggest(new SuggestBuilder()
                    .addSuggestion("MySuggestion",
                            SuggestBuilders.completionSuggestion("suggestion")
                    .prefix(dto.getSearchWords())
                            .skipDuplicates(true)
                            .size(dto.getPageSize())
                    ));
        //1.执行搜索
        try {
            SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);

            //定义一个List集合
            List<String> suggestionList=new ArrayList<>();

            //4.解析数据
            Suggest suggest = response.getSuggest();
            CompletionSuggestion mySuggestion = suggest.getSuggestion("MySuggestion");
            List<CompletionSuggestion.Entry.Option> options = mySuggestion.getOptions();
            for (CompletionSuggestion.Entry.Option option : options) {
                suggestionList.add(option.getText().toString());
            }

            return ResponseResult.okResult(suggestionList);
        } catch (IOException e) {
            e.printStackTrace();
            return ResponseResult.errorResult(AppHttpCodeEnum.SERVER_ERROR);
        }
    }
}
